[contents] [prev] [next] [top] [bottom] (4 out of 7)

Defining Methods

This section describes the ScriptX syntax for defining methods. It also describes common ways of using method definition and redefinition to make your class effectively interact with other classes.

Methods and Generic Functions

You call a method by calling a generic function on an object; the generic function redirects this function call to the appropriate method. Chapter 3, "Working with Objects," describes the relationship between methods, defined by classes and objects, and generic functions, which are used to access those methods. For a definition of generic functions, see page 17.

When you define methods using the expressions described in this section, you do not have to do anything extra to create a new generic function or to make sure the existing generic function knows about your new definition. ScriptX automatically updates the appropriate generic function for your method definition or creates a new generic function for new methods.

The generic function you name, and thus, the one you specialize, is the one that is visible in the current module. You can have many different generic functions of the same name in different modules. For information on names and modules, see "Modules" on page 183.

Method Definition Syntax

Method definitions are almost identical to function definitions. Unlike functions, method definitions require at least one argument-the object or class on which the method is invoked. That argument is typically called self, and it is always the first argument to that method.

Because method and function definitions are so similar, this section provides only a general summary of the syntax itself. For more details, see the section "Defining Functions" on page 95 of Chapter 5, "Functions, Threads and Pipes." Methods are defined using the following general syntax.

method methodName  self arguments -> body
where:

A method can also have keyword arguments, as described in "Defining init Methods" on page 133. However, since they complicate the syntax and are often not needed, they are described later.

As elsewhere, you must refer to class and instance variables in the body of a method definition using the standard class and instance variable access expression (self.variable ). ScriptX does not provide a mechanism for automatically accessing class and instance variables by name within a method definition.

Methods, like functions, can use the return expression to specify the value the method returns. Without an explicit return, the method returns the value of the last expression evaluated. When in doubt on a return value, consider returning self, undefined, or OK.

object myObj (RootObject)
	inst vars a:500
	inst methods
	method incrementA self inc -> (
		self.a := self.a + inc
	)
end

class GenericClass (RootObject)
	instance methods
	method printClass self -> (
		print ("I'm an instance of " + (getClassName self))
	)
	method printMe self -> (
		format debug "This is me: %*.\n" self @normal 
		printClass self
	)
end

global myGenericClass := new GenericClass
printMe myGenericClass
This is me: GenericClass@0x1161b7c
	"I'm an instance of GenericClass"

class MyLL (Array)
	instance methods
	method addItemToBeginning self item -> (
		addNth self 1 item
		format debug "%* added " item @normal
		return self
	)
end

global myLinkedList := new MyLL
addItemToBeginning myLinkedList 12345
12345 added #(12345) as MyLL

More Examples

The following class, MyClass, defines three methods:

class MyClass ()
instance vars a,b,c
instance methods
method addAllIVs self -> (
self.a + self.b + self.c
)
	method addEmUp self #rest allArgs -> (
local s := 0
for i in allArgs do (s := s + i)
s := s + (addAllIVs self)
)
	method changeIVs self #key incA:(10) incB:(10) incC:(10) -> (
self.a := self.a + incA
self.b := self.b + incB
self.c := self.c + incC
print self.a; print self.b; print self.c
return self
)
end

Now that the class and its methods have been defined, here are some examples of its use:

object exmpl (MyClass)
	settings a:1, b:5, c:12
end
exmpl.a
1
exmpl.b
5
exmpl.c
12
addAllIVs exmpl
18
addEmUp exmpl 3 8 4 6
39
changeIVs exmpl -- no keywords, defaults are all 10
11
15
22
changeIVs exmpl incA:4 incB:-7
15
8
32

Free Method Syntax

Free methods are methods that can be defined outside the boundaries of a class or object definition. Free methods are useful for adding or redefining method definitions in existing classes or objects without having to redefine the entire class or object.

Free method definitions look similar to regular method definitions, with the addition of a special clause that specifies the class or object to which this method belongs. There are two forms of method definition: one for adding instance methods to an object, and one for adding either class or instance methods to a class.

method methodName self { object object } args -> body
[ class ] method methodName self { class class } args -> body
In both forms, methodName is the name of the generic function that invokes this method. If the class reserved word is included before the method reserved word, that method is a class method.

The args part of each free method definition supplies the arguments this method takes (besides self). These can be positional arguments, rest arguments, or keyword arguments. See Chapter 5, "Functions, Threads and Pipes," for a description of each of these types of arguments.

The expression within braces is called a restriction, and is used to point to the class or object that the method is associated with. Note that the restriction must come after the self argument, but before any other arguments.

Finally, the body part of the method definition is the expression, often a compound expression, that the method evaluates when invoked.

The first form adds an instance method to the object specified by object. The expression in braces holds an object, and can be one of the following expressions, where appropriate:

Here is an example:

tryThisOut := #(1,2)
method appendSum self {object tryThisOut} n m -> 
	(append self (n + m); return self)
appendSum tryThisOut 3 4
#(1,2,7)

The second form adds either an instance method or a class method to the class specified by class, which can contain one of the expressions from the list above for object.

class MyClass () end
method jellydonut self {class MyClass} name -> 
	format debug "%* is not a jellydonut!\n" name @unadorned

i := new MyClass
jellydonut i "cruller"
"cruller is not a jellydonut!"

Adding Methods to the ScriptX Core Classes

Using free method definitions, it is possible to add new methods to those ScriptX core classes that are not sealed. This allows you to extend the behavior of those classes, and of classes that inherit from those classes.

When adding methods to the core classes, be careful not to override existing methods. That is, avoid defining a method of the same name as one that already exists in that class, particularly initialization (init and afterInit) methods. A class may depend on internal behavior defined by those methods; overriding those methods may cause errors in the operation of that class.

As an alternative to overriding existing methods in the ScriptX Core Classes, consider creating a subclass of that class with your own definitions instead.

Overriding Methods

The previous sections described how to create entirely new methods for your class or object. However, the other important use of method definitions is to override an existing method. Overriding a method means providing a different implementation for an inherited method. This can happen either in class specialization or instance specialization. For an example of class specialization, TwoDShape defines the draw method; a subclass of TwoDShape that re-implements the draw method is said to override the draw defined in its superclass. For an example of instance specialization, an instance of TwoDShape that re-implements the draw method is said to override the draw defined in its class.

When you override an existing method, your method must have the same name and positional arguments as the original method, but can have new keyword arguments. In addition, the method can use the functionality provided by superclasses by calling nextMethod. As such, there are two ways to override an existing method:

The first option reuses the existing implementation, while the latter does not. In either case, you can provide your own specialization in the body of the method. The latter option was described in the previous section "Method Definition Syntax" on page 126.

You use nextMethod to call the overriding method from the body of a method, like this:

method methodName self arguments -> (
. . . optionally do something here . . .
nextMethod self arguments
. . . optionally do something else here . . .
)
The nextMethod expression passes the call upward through the inheritance hierarchy, calling methods with the same name, so that each superclass can invoke its own implementation of that method on the instance. Another way to look at this is that invoking nextMethod simply allows you to call the original methods that would have been invoked had you not overridden. Each nextMethod encountered calls the next method up the chain, hence the term nextMethod.

Since nextMethod calls methods in superclasses, you should supply to nextMethod any arguments that you want those superclasses to handle. For example, since the draw method in TwoDShape takes three arguments (self, surface, clip), when you create a subclass of TwoDShape where you override draw, you would call nextMethod with those same arguments:

method draw surface clip -> (
	nextMethod self surface clip 
)

In another example, the following method definition overrides the append method for a given object such that the original append is called only if the item to be appended to this object is an instance of the ImmediateInteger class. (The append method is defined in the Sequence class.) Otherwise, an error message is printed to the debug stream and the method returns undefined:

global myArrayOfIntegers := #()

-- override the append method on myArrayOfIntegers
method append self {object myArrayOfIntegers} item -> (
	if (getClass item = ImmediateInteger) then (
		nextMethod self item
	)
	else (
		format debug "Not an ImmediateInteger: %*\n" item @normal
		return undefined
	)
)

Multiple Inheritance with nextMethod

Technically, because ScriptX supports multiple inheritance, a method can have more than two chains of inheritance up to RootObject. In this case, nextMethod calls the next method in depth-first order, as described in "Multiple Inheritance" on page 122. Use getSupers to see the order in which the methods are called. For example, given a class that inherits from both TwoDShape and Bounce, nextMethod calls the classes in the order shown here:

class BouncingShape (TwoDShape, Bounce)
end

getSupers BouncingShape | print
#<Class Substrate:Bounce>
#<Class Substrate:TwoDController>
#<Class Substrate:Controller>
#<Class Substrate:IndirectCollection>
#<Class Substrate:Collection>
#<Class Substrate:TwoDShape>
#<Class Substrate:TwoDPresenter>
#<Class Substrate:Presenter>
#<Class Substrate:RootObject>

The ScriptX core classes provide mechanisms to define additional behavior for certain methods and protocols in the core classes. For example, if you are interested in overriding any of the methods that add or delete items in collections, as the example above does, consider the advantages of using the IndirectCollection class. For more information, see the "Collections" chapter of the ScriptX Components Guide.


This document is part of the ScriptX Language Guide, one of the volumes of the ScriptX Technical Reference Series. ScriptX is developed by the ScriptX Engineering Team at Apple Computer, successor to the Kaleida Engineering Team at Kaleida Labs, Inc.

Copyright 1996 Apple Computer, Inc. All Rights Reserved.